package byteBuddy;

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.agent.ByteBuddyAgent;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.agent.builder.ResettableClassFileTransformer;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.dynamic.loading.ClassReloadingStrategy;
import net.bytebuddy.implementation.FixedValue;
import net.bytebuddy.matcher.ElementMatcher;
import net.bytebuddy.matcher.ElementMatchers;
import net.bytebuddy.pool.TypePool;
import org.junit.Test;

import java.io.File;
import java.lang.instrument.Instrumentation;
import java.lang.reflect.InvocationTargetException;

import static net.bytebuddy.matcher.ElementMatchers.*;

@Slf4j
public class ByteBuddyTest {

    @SneakyThrows
    @Test
    public void testSub() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Foo foo = new Foo();
        log.info(foo.name());

        Instrumentation instrumentation = ByteBuddyAgent.install();
        File file = new File("bytebuddyclass");
        if (!file.exists()) {
            file.mkdir();
        }
        DynamicType.Unloaded<Foo> unloadedA = new ByteBuddy()
                .subclass(Foo.class)
                .method(ElementMatchers.named("name"))
                .intercept(FixedValue.value("Hello World!"))
                .make();
        unloadedA.saveIn(file);
        Class<? extends Foo> dynamicTypeA = unloadedA
                .load(getClass().getClassLoader())
                .getLoaded();
        log.info(dynamicTypeA.getConstructor().newInstance().name());
        log.info(foo.name());


        Class<? extends Foo> dynamicTypeB = new ByteBuddy()
                .redefine(Foo.class)
                .method(ElementMatchers.named("name"))
                .intercept(FixedValue.value("Hello World-b!"))
                .make()
                .load(Foo.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent())
                .getLoaded();
        log.info(dynamicTypeB.getConstructor().newInstance().name());
        log.info(foo.name());


        DynamicType.Unloaded<? extends Foo> unloaded = new ByteBuddy()
                .redefine(dynamicTypeB)
                .method(ElementMatchers.named("name"))
                .intercept(FixedValue.value("Hello World-b-c!"))
                .make();

        unloaded.saveIn(file);
        Class<? extends Foo> dynamicTypeC = unloaded
                .load(Foo.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent())
                .getLoaded();
        log.info(dynamicTypeC.getConstructor().newInstance().name());
        log.info(foo.name());

        TypePool typePool = TypePool.Default.of(getClass().getClassLoader());
        TypeDescription resolve = typePool.describe("byteBuddy.Foo").resolve();
        System.out.println(resolve);


        AgentBuilder agentBuilder = new AgentBuilder.Default(new ByteBuddy()).ignore(nameStartsWith("net.bytebuddy.")
                .or(nameContains("javassist"))
                .or(nameContains(".asm."))
                .or(nameStartsWith("sun.reflect"))
                // 跳过已经被其它插件处理的类
                .or(ElementMatchers.<TypeDescription>isSynthetic()))
                .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
                .disableClassFormatChanges()
                .type(named("byteBuddy.Foo"))
                .transform((builder, type, classLoader, module) -> {


                    ElementMatcher.Junction methodMatcher = named("name");

                    return builder
                            .visit(Advice.to(MockMethodAdvice.class)
                                    .on(methodMatcher))
                            ;
                });

        agentBuilder = agentBuilder
                .with(AgentBuilder.Listener.StreamWriting.toSystemError().withTransformationsOnly());
        ResettableClassFileTransformer classFileTransformer = agentBuilder.installOn(instrumentation);
        log.info(foo.name());
    }
}
